<!DOCTYPE html>
<html lang="zh-CN">
  <head>
  <meta charset="UTF-8">
  <meta 
    name="viewport"
    content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
  <meta 
    http-equiv="X-UA-Compatible" 
    content="ie=edge">
  <meta 
    name="theme-color" 
    content="#fff" 
    id="theme-color">
  <meta 
    name="description" 
    content="霜序廿的个人网站">
  <link 
    rel="icon" 
    href="https://api2.mubu.com/v3/photo/654b368e-b847-4122-982c-86d90b3f5275.jpg">
  <title>彻底搞懂 Node.js 中的 Require 机制(源码分析到手写实践)</title>
  
    
      <meta 
        property="og:title" 
        content="彻底搞懂 Node.js 中的 Require 机制(源码分析到手写实践)">
    
    
      <meta 
        property="og:url" 
        content="https://shuangxunian.github.io/2021/06/13/understandTheRequireImplementationPrinciple/index.html">
    
    
      <meta 
        property="og:img" 
        content="https://api2.mubu.com/v3/photo/654b368e-b847-4122-982c-86d90b3f5275.jpg">
    
    
      <meta 
        property="og:img" 
        content="关于require的实现原理">
    
    
      <meta 
        property="og:type" 
        content="article">
      <meta 
        property="og:article:published_time" 
        content="2021-06-13">
      <meta 
        property="og:article:modified_time" 
        content="2020-11-26">
      <meta 
        property="og:article:author" 
        content="霜序廿">
      
        
          <meta 
            property="og:article:tag" 
            content="js">
        
      
    
  
  <script>
    function loadScript(url, cb) {
      var script = document.createElement('script');
      script.src = url;
      if (cb) script.onload = cb;
      script.async = true;
      document.body.appendChild(script);
    }
    function loadCSS(href, data, attr) {
      var sheet = document.createElement('link');
      sheet.ref = 'stylesheet';
      sheet.href = href;
      sheet.dataset[data] = attr;
      document.head.appendChild(sheet);
    }
    function changeCSS(cssFile, data, attr) {
      var oldlink = document.querySelector(data);
      var newlink = document.createElement("link");
      newlink.setAttribute("rel", "stylesheet");
      newlink.setAttribute("href", cssFile);
      newlink.dataset.prism = attr;
      document.head.replaceChild(newlink, oldlink);
    }
  </script>
  
    
      
      
      
      
        
        
        
        <script>
          function prismThemeChange() {
            if(document.getElementById('theme-color').dataset.mode === 'dark') {
              if(document.querySelector('[data-prism]')) {
                changeCSS('/js/lib/prism/prism-tomorrow.min.css', '[data-prism]', 'prism-tomorrow');
              } else {
                loadCSS('/js/lib/prism/prism-tomorrow.min.css', 'prism', 'prism-tomorrow');
              }
            } else {
              if(document.querySelector('[data-prism]')) {
                changeCSS('/js/lib/prism/prism.min.css', '[data-prism]', 'prism');
              } else {
                loadCSS('/js/lib/prism/prism.min.css', 'prism', 'prism');
              }
            }
          }
          prismThemeChange()
        </script>
      
      
        
        <link rel="stylesheet" href="/js/lib/prism/prism-line-numbers.min.css">
      
    
  
  <script>
    // control reverse button
    var reverseDarkList = {
      dark: 'light',
      light: 'dark'
    };
    var themeColor = {
      dark: '#1c1c1e',
      light: '#fff'
    }
    // get the data of css prefers-color-scheme
    var getCssMediaQuery = function() {
      return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    };
    // reverse current darkmode setting function
    var reverseDarkModeSetting = function() {
      var setting = localStorage.getItem('user-color-scheme');
      if(reverseDarkList[setting]) {
        setting = reverseDarkList[setting];
      } else if(setting === null) {
        setting = reverseDarkList[getCssMediaQuery()];
      } else {
        return;
      }
      localStorage.setItem('user-color-scheme', setting);
      return setting;
    };
    // apply current darkmode setting
  </script>
  
    <script>
      var setDarkmode = function(mode) {
      var setting = mode || localStorage.getItem('user-color-scheme');
      if(setting === getCssMediaQuery()) {
        document.documentElement.removeAttribute('data-user-color-scheme');
        localStorage.removeItem('user-color-scheme');
        document.getElementById('theme-color').content = themeColor[setting];
        document.getElementById('theme-color').dataset.mode = setting;
        prismThemeChange();
      } else if(reverseDarkList[setting]) {
        document.documentElement.setAttribute('data-user-color-scheme', setting);
        document.getElementById('theme-color').content = themeColor[setting];
        document.getElementById('theme-color').dataset.mode = setting;
        prismThemeChange();
      } else {
        document.documentElement.removeAttribute('data-user-color-scheme');
        localStorage.removeItem('user-color-scheme');
        document.getElementById('theme-color').content = themeColor[getCssMediaQuery()];
        document.getElementById('theme-color').dataset.mode = getCssMediaQuery();
        prismThemeChange();
      }
    };
    setDarkmode();
    </script>
  
  
  <link rel="preload" href="//at.alicdn.com/t/font_1946621_i1kgafibvw.css" as="style" >
  <link rel="preload" href="//at.alicdn.com/t/font_1952792_89b4ac4k4up.css" as="style" >
  
  
    <link rel="preload" href="/js/lib/lightbox/baguetteBox.min.js" as="script">
    <link rel="preload" href="/js/lib/lightbox/baguetteBox.min.css" as="style" >
  
  
    <link rel="preload" href="/js/lib/lozad.min.js" as="script">
  
  
  
  
  
  
  
  <link rel="stylesheet" href="/css/main.css">
  
  <link rel="stylesheet" href="//at.alicdn.com/t/font_1946621_i1kgafibvw.css">
  
  <link rel="stylesheet" href="//at.alicdn.com/t/font_1952792_89b4ac4k4up.css">
  
    <link rel="stylesheet" href="/js/lib/lightbox/baguetteBox.min.css">
  
<meta name="generator" content="Hexo 5.4.0"></head>

  <body>
    <div class="wrapper">
       
      <nav class="navbar">
  <div class="navbar-logo">
    <span class="navbar-logo-main">
      
        <img 
          class="navbar-logo-img" 
          src="https://api2.mubu.com/v3/photo/654b368e-b847-4122-982c-86d90b3f5275.jpg" 
          alt="blog logo">
      
      <span class="navbar-logo-dsc">霜序廿的个人网站</span>
    </span>
  </div>
  <div class="navbar-menu">
    
      <a 
        href="/" 
        class="navbar-menu-item">
        
          首页
        
      </a>
    
      <a 
        href="/archives" 
        class="navbar-menu-item">
        
          归档
        
      </a>
    
      <a 
        href="/tags" 
        class="navbar-menu-item">
        
          标签
        
      </a>
    
      <a 
        href="/categories" 
        class="navbar-menu-item">
        
          分类
        
      </a>
    
      <a 
        href="/about" 
        class="navbar-menu-item">
        
          关于
        
      </a>
    
      <a 
        href="/links" 
        class="navbar-menu-item">
        
          友链
        
      </a>
    
    <a 
      class="navbar-menu-item darknavbar" 
      id="dark">
      <i class="iconfont icon-weather"></i>
    </a>
    <a 
      class="navbar-menu-item searchnavbar" 
      id="search">
      <i 
        class="iconfont icon-search" 
        style="font-size: 1.2rem; font-weight: 400;">
      </i>
    </a>
  </div>
</nav> 
      
      <div 
        id="local-search" 
        style="display: none">
        <input
          class="navbar-menu-item"
          id="search-input"
          placeholder="请输入搜索内容..." />
        <div id="search-content"></div>
      </div>
      
      <div class="section-wrap">
        <div class="container">
          <div class="columns">
            <main class="main-column">
<article class="card card-content">
  <header>
    <h1 class="post-title">
      彻底搞懂 Node.js 中的 Require 机制(源码分析到手写实践)
    </h1>
  </header>
  <div class="post-meta post-show-meta">
    <time datetime="2021-06-13T13:48:56.360Z">
      <i 
        class="iconfont icon-calendar" 
        style="margin-right: 2px;">
      </i>
      <span>2021-06-13</span>
    </time>
    
      <span class="dot"></span>
      
        <a 
          href="/categories/%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/" 
          class="post-meta-link">
          技术文章
        </a>
      
    
    
      <span class="dot"></span>
      <span>5.1k 字</span>
    
  </div>
  
    <div 
      class="post-meta post-show-meta" 
      style="margin-top: -10px;">
      <div style="display: flex; align-items: center;">
        <i 
          class="iconfont icon-biaoqian" 
          style="margin-right: 2px; font-size: 1.15rem;">
        </i>
        
          
          <a 
            href="/tags/js/" 
            class="post-meta-link">
            js
          </a>
        
      </div>
    </div>
  
  </header>
  <div 
    id="section" 
    class="post-content">
    <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本文你能学到：</p>
<ol>
<li>自己手写实现一个 require，面试用也可以。</li>
<li>如何看 Node.js 源码</li>
<li>require 函数是如何产生的？为什么在 module 中可以直接使用。</li>
<li>require 加载原生模块时候如何处理的，为什么 require(‘net’) 可以直接找到</li>
<li>Node.js 中 require 会出现循环引用问题吗？</li>
<li>require 是同步还是异步的？为什么？</li>
<li>exports 和 module.exports 的区别是什么？</li>
<li>你知道 require 加载的过程中使用了 vm 模块吗？vm 模块是做什么的？vm 模块除了 require 源码用到还有哪些应用场景。</li>
</ol>
<h2 id="什么是-CommonJS"><a href="#什么是-CommonJS" class="headerlink" title="什么是 CommonJS"></a>什么是 CommonJS</h2><p>每一个文件就是一个模块，拥有自己独立的作用域，变量，以及方法等，对其他的模块都不可见。CommonJS 规范规定，每个模块内部，module 变量代表当前模块。这个变量是一个对象，它的exports属性（即module.exports）是对外的接口。</p>
<h2 id="模块分类"><a href="#模块分类" class="headerlink" title="模块分类"></a>模块分类</h2><ul>
<li>原生(核心)模块：Node 提供的模块我们都称之为原生模块<ul>
<li>内建模块:Node.js 原生提供的模块中，由纯 C/C++ 编写的称为内建模块</li>
<li>全局模块：Node.js在启动时，会生成一个全局量 process</li>
<li>除了上面两种可以直接 require 的所有原生模块</li>
</ul>
</li>
<li>文件模块：用户编写的模块<ul>
<li>普通文件模块：node_modules 下面的模块，或者我们自己开发时候写的每个文件内容。</li>
<li>C++ 扩展模块：用户自己编写的 C++ 扩展模块或者第三方 C++ 扩展模块</li>
</ul>
</li>
</ul>
<h2 id="模块加载"><a href="#模块加载" class="headerlink" title="模块加载"></a>模块加载</h2><p>介绍了上面的模块分类，正常应该到介绍不同模块的加载环节，这里不这样,只列出目录。先带你看一遍源码，再手写一下，然后我想你自己总结一下这几种模块的加载区别。<br><strong>加载 Node.js 原生模块</strong><br>本文不包括直接调用内建纯C/C++模块，也不推荐这样使用，因为我们正常调用的原生模块都是通过 js封装一层，它们自己再去调用，你想直接调用的 Node.js提供的存C/C++ 内建模块，js 封装的一层也都能做到。那部分内容放在 Node.js与 C++ 那些事的文章中介绍。<br><strong>require 加载普通文件模块</strong><br><strong>require 加载 C++ 扩展文件模块</strong></p>
<h2 id="require-加载原理-源码分析与手写"><a href="#require-加载原理-源码分析与手写" class="headerlink" title="require 加载原理(源码分析与手写)"></a>require 加载原理(源码分析与手写)</h2><p>require 源码并不复杂，这里采用的是边看源码边手写的方式讲解(我们最终实现的require 是简易版本，一些源码提到，但是简易版本不会实现)，实现 require 其实就是实现整个 Node.js 的模块加载机制，Node.js 的模块加载机制总结下来一共八步。</p>
<h3 id="1-基础准备阶段"><a href="#1-基础准备阶段" class="headerlink" title="1.基础准备阶段"></a>1.基础准备阶段</h3><p>Node.js 模块加载的主流程都在 Module 类中，在源码的 <a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L150">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L150</a> 中进行了基础 Module 类定义，这个构造函数中的内容主要做一些值的初始化，我们自己对照着实现下，为了和源码有一个区别，本文使用 KoalaModule 命名。</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">KoalaModule</span><span class="token punctuation">(</span>id <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>id <span class="token operator">=</span> id<span class="token punctuation">;</span>       <span class="token comment">// 这个id其实就是我们require的路径</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>path <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token comment">// path是Node.js内置模块，用它来获取传入参数对应的文件夹路径</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#125;</span><span class="token punctuation">;</span>        <span class="token comment">// 导出的东西放这里，初始化为空对象</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>filename <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span>     <span class="token comment">// 模块对应的文件名</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>loaded <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>      <span class="token comment">// loaded用来标识当前模块是否已经加载</span>
<span class="token punctuation">&#125;</span>

KoalaModule<span class="token punctuation">.</span>_cache <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//创建一个空的缓存对象</span>
KoalaModule<span class="token punctuation">.</span>_extensions <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 创建一个空的扩展点名类型函数对象(后面会知道用来做什么)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>然后在源码中你会找到 require 函数,在 KoalaModule 的原型链上，我们实现下</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">Module</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">require</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">id</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">return</span> Module<span class="token punctuation">.</span><span class="token function">_load</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token comment">/* isMain */</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
<p>在源码中你会发现又调用了_load函数，找到源码中的 _load 函数，(源码位置：<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L724">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L724</a>) 下面的所有步骤都是在这个函数中完成调用和 return的，实现简易版_load函数。</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">KoalaModule<span class="token punctuation">.</span><span class="token function-variable function">_load</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">request</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>    <span class="token comment">// request是我们传入的路劲参数</span>
<span class="token comment">// 2.路径分析并定位到文件</span>
    <span class="token keyword">const</span> filename <span class="token operator">=</span> KoalaModule<span class="token punctuation">.</span><span class="token function">_resolveFilename</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 3.判断模块是否加载过(缓存判断)</span>
    <span class="token keyword">const</span> cachedModule <span class="token operator">=</span> koalaModule<span class="token punctuation">.</span>_cache<span class="token punctuation">[</span>filename<span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>cachedModule <span class="token operator">!==</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">return</span> cachedModule<span class="token punctuation">.</span>exports<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    <span class="token comment">// 4. 去加载 node 原生模块中</span>
    <span class="token comment">/*const mod = loadNativeModule(filename, request);
    if (mod &amp;&amp; mod.canBeRequiredByUsers) return mod.exports;*/</span>

    <span class="token comment">// 5. 如果缓存不存在，我们需自行加载模块，new 一个 KoalaModule实例</span>
    <span class="token comment">// 加载完成直接返回module.exports</span>
    <span class="token keyword">const</span> module <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">KoalaModule</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// 6. load加载之前加入缓存，这也是不会造成循环引用问题的原因，但是循环引用，这个缓存里面的exports可能还没有或者不完整</span>
    KoalaModule<span class="token punctuation">.</span>_cache<span class="token punctuation">[</span>filename<span class="token punctuation">]</span> <span class="token operator">=</span> module<span class="token punctuation">;</span>
    <span class="token comment">// 7. module.load 真正的去加载代码</span>
    module<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 8. 返回模块的module.exports </span>
    <span class="token keyword">return</span> module<span class="token punctuation">.</span>exports<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>这个函数的源码中有一些其他逻辑的细节判断，有兴趣的小伙伴再学习下，我提出了核心主干。</p>
<h3 id="2-路径分析并定位到文件"><a href="#2-路径分析并定位到文件" class="headerlink" title="2.路径分析并定位到文件"></a>2.路径分析并定位到文件</h3><p>找到源码中的 _resolveFilename 函数，这个方法是通过用户传入的require参数来解析到真正的文件地址。(源码地址：<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L816">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L816</a>)<br>这个函数源码中比较复杂，因为 require传递过来的值需要一层一层的判断，同时支持多种参数：内置模块，相对路径，绝对路径，文件夹和第三方模块等等，如果是文件夹或者第三方模块还要解析里面的 package.json 和 index.js。这里简单处理，只实现通过相对路径和绝对路径来查找文件，并支持判断文件js和json后缀名判断:</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">KoalaModule<span class="token punctuation">.</span><span class="token function-variable function">_resolveFilename</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">request</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">const</span> filename <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment">// 获取传入参数对应的绝对路径</span>
    <span class="token keyword">const</span> extname <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">extname</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 获取文件后缀名</span>
    <span class="token comment">// 如果没有文件后缀名，判断是否可以添加.js和.json</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>extname<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">const</span> exts <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>KoalaModule<span class="token punctuation">.</span>_extensions<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> exts<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token keyword">const</span> currentPath <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>filename<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>exts<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span>
            <span class="token comment">// 如果拼接后的文件存在，返回拼接的路径</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">existsSync</span><span class="token punctuation">(</span>currentPath<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token keyword">return</span> currentPath<span class="token punctuation">;</span>
            <span class="token punctuation">&#125;</span>
        <span class="token punctuation">&#125;</span>
    <span class="token punctuation">&#125;</span>
    <span class="token keyword">return</span> filename<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="3-判断模块是否加载过-缓存判断"><a href="#3-判断模块是否加载过-缓存判断" class="headerlink" title="3.判断模块是否加载过(缓存判断)"></a>3.判断模块是否加载过(缓存判断)</h3><p>判断这个找到的模块文件是否缓存过，如果缓存过，直接返回 cachedModule.exports, 这里就会想到一个问题为什么在 Node.js 中模块重复引用也不会又性能问题，因为做了缓存。(源码位置：<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L747">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L747</a>)</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> cachedModule <span class="token operator">=</span> koalaModule<span class="token punctuation">.</span>_cache<span class="token punctuation">[</span>filename<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>cachedModule <span class="token operator">!==</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">return</span> cachedModule<span class="token punctuation">.</span>exports<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<h3 id="4-去加载-node-原生模块"><a href="#4-去加载-node-原生模块" class="headerlink" title="4.去加载 node 原生模块"></a>4.去加载 node 原生模块</h3><p>如果没有进行缓存过，会调用一个加载原生模块的函数，这里分析一下。<br><strong>注意：</strong>第四部分代码我们没有进行手写实现，在_load中进行了注释，但是这里进行了一遍分析，我们写的代码是如何调用到原生模块，本部分涉及到你可能会不想看的C内容，其实也可以忽略掉，过一遍就能知道最后的结论为什么是那样了，不然看了书记不住为什么这样。<br>比如我们require(net),走完前面的缓存判断就会到达这个loadNativeModule函数(源码位置:<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/802c98d65de40f245781f591a0b3b38336d1af94/lib/internal/modules/cjs/helpers.js#L31">https://github.com/nodejs/node/blob/802c98d65de40f245781f591a0b3b38336d1af94/lib/internal/modules/cjs/helpers.js#L31</a>)</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> mod <span class="token operator">=</span> <span class="token function">loadNativeModule</span><span class="token punctuation">(</span>filename<span class="token punctuation">,</span> request<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>mod <span class="token operator">&amp;&amp;</span> mod<span class="token punctuation">.</span>canBeRequiredByUsers<span class="token punctuation">)</span> <span class="token keyword">return</span> mod<span class="token punctuation">.</span>exports<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p>继续往下看 loadNativeModule 函数的调用。</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">function</span> <span class="token function">loadNativeModule</span><span class="token punctuation">(</span><span class="token parameter">filename<span class="token punctuation">,</span> request</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">// 这里判断下是不是在原生js模块中  ，NativeModule在bootstrap/loader.js中定义</span>
    <span class="token keyword">const</span> mod <span class="token operator">=</span> NativeModule<span class="token punctuation">.</span>map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>mod<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">'load native module %s'</span><span class="token punctuation">,</span> request<span class="token punctuation">)</span><span class="token punctuation">;</span>
        mod<span class="token punctuation">.</span><span class="token function">compileForPublicLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> mod<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>mod 是一个 NativeModule 对象，这个对象很常见，在 node启动一个文件时候也会用到(源码位置：<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/802c98d65de40f245781f591a0b3b38336d1af94/lib/internal/bootstrap/loaders.js#L161">https://github.com/nodejs/node/blob/802c98d65de40f245781f591a0b3b38336d1af94/lib/internal/bootstrap/loaders.js#L161</a>)<br>然后到了 mod 的核心函数mod.compileForPublicLoader();,下面这段代码相对源码删除了一些非核心部分。</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">compileForPublicLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  
    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">compileForInternalLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">;</span>  
<span class="token punctuation">&#125;</span>  

<span class="token function">compileForInternalLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>loaded <span class="token operator">||</span> <span class="token keyword">this</span><span class="token punctuation">.</span>loading<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  
        <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">;</span>  
    <span class="token punctuation">&#125;</span>  
    <span class="token comment">// id就是我们要加载的模块，比如net </span>
    <span class="token keyword">const</span> id <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>id<span class="token punctuation">;</span>  
    <span class="token keyword">this</span><span class="token punctuation">.</span>loading <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  
    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>  
        <span class="token keyword">const</span> fn <span class="token operator">=</span> <span class="token function">compileFunction</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>  
        <span class="token function">fn</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> nativeModuleRequire<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">,</span> process<span class="token punctuation">,</span> internalBinding<span class="token punctuation">,</span> primordials<span class="token punctuation">)</span><span class="token punctuation">;</span>  
        <span class="token keyword">this</span><span class="token punctuation">.</span>loaded <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>  
    <span class="token punctuation">&#125;</span> <span class="token keyword">finally</span> <span class="token punctuation">&#123;</span>  
        <span class="token keyword">this</span><span class="token punctuation">.</span>loading <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>  
    <span class="token punctuation">&#125;</span>
    <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">;</span>  
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>我们重点看compileFunction这里的逻辑。该函数是node_native_module_env.cc模块导出的函数。具体的代码就不贴了，通过层层查找，最后到 node_native_module.cc 的NativeModuleLoader::CompileAsModule。</p>
<pre class="line-numbers language-c++" data-language="c++"><code class="language-c++">MaybeLocal&lt;Function&gt; NativeModuleLoader::CompileAsModule(  
Local&lt;Context&gt; context,  
const char* id,  
NativeModuleLoader::Result* result) &#123;  
    Isolate* isolate &#x3D; context-&gt;GetIsolate();  
    &#x2F;&#x2F; 函数的形参  
    std::vector&lt;Local&lt;String&gt;&gt; parameters &#x3D; &#123;  
        FIXED_ONE_BYTE_STRING(isolate, &quot;exports&quot;),  
        FIXED_ONE_BYTE_STRING(isolate, &quot;require&quot;),  
        FIXED_ONE_BYTE_STRING(isolate, &quot;module&quot;),  
        FIXED_ONE_BYTE_STRING(isolate, &quot;process&quot;),  
        FIXED_ONE_BYTE_STRING(isolate, &quot;internalBinding&quot;),  
        FIXED_ONE_BYTE_STRING(isolate, &quot;primordials&quot;)
    &#125;;  
    &#x2F;&#x2F; 编译出一个函数  
    return LookupAndCompile(context, id, &amp;parameters, result);  
&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>我们继续看LookupAndCompile。</p>
<pre class="line-numbers language-c++" data-language="c++"><code class="language-c++">MaybeLocal&lt;Function&gt; NativeModuleLoader::LookupAndCompile(
    Local&lt;Context&gt; context,
    const char* id,
    std::vector&lt;Local&lt;String&gt; &gt;* parameters,
    NativeModuleLoader::Result* result )&#123;
    Isolate			* isolate &#x3D; context-&gt;GetIsolate();
    EscapableHandleScope	scope( isolate );

    Local&lt;String&gt; source;
    &#x2F;* 找到原生js模块的地址 *&#x2F;
    if ( !LoadBuiltinModuleSource( isolate, id ).ToLocal( &amp;source ) )
    &#123;
        return(&#123;&#125;);
    &#125;
    &#x2F;* ‘net’ + ‘.js’ *&#x2F;
    std::string	filename_s	&#x3D; id + std::string( &quot;.js&quot; );
    Local&lt;String&gt;	filename	&#x3D;
        OneByteString( isolate, filename_s.c_str(), filename_s.size() );
    &#x2F;*
    * 省略一些参数处理
    * 脚本源码
    *&#x2F;
    ScriptCompiler::Source script_source( source, origin, cached_data );
    &#x2F;* 编译出一个函数 *&#x2F;
    MaybeLocal&lt;Function&gt; maybe_fun &#x3D;
        ScriptCompiler::CompileFunctionInContext( context,
                            &amp;script_source,
                            parameters-&gt;size(),
                            parameters-&gt;data(),
                            0,
                            nullptr,
                            options );
    Local&lt;Function&gt; fun &#x3D; maybe_fun.ToLocalChecked();
    return(scope.Escape( fun ) );
&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>LookupAndCompile 函数首先找到加载模块的源码，然后编译出一个函数。我们看一下LoadBuiltinModuleSource 如何查找模块源码的。</p>
<pre class="line-numbers language-c++" data-language="c++"><code class="language-c++">MaybeLocal&lt;String&gt; NativeModuleLoader::LoadBuiltinModuleSource(Isolate* isolate, const char* id) &#123;  
    const auto source_it &#x3D; source_.find(id);  
    return source_it-&gt;second.ToStringChecked(isolate);  
&#125;<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p>这里是 id 是 net，通过该 id 从 _source 中找到对应的数据，那么_source 是什么呢？Nodejs 为了提高效率，把原生 js 模块的源码字符串直接转成 ascii 码存到<strong>内存</strong>里。这样加载这些模块的时候，就不需要硬盘 io 了。直接从内存读取就行。_source 的定义（在 node_javascript.cc里，负责编译nodejs 源码或者执行 js2c.py 生成）。<br>结论：Node.js 在启动时候直接从内存中读取内容，我们通过 require 加载 net 原生模块时，通过 NativeModule的compileForInternalLoader，最终会在 _source 中找到对应的源码字符串，然后编译成一个函数，然后去执行这个函数,执行函数的时候传递 nativeModuleRequire和internalBinding两个函数，nativeModuleRequire用于加载原生 js 模块，internalBinding用于加载纯C++ 编写的内置模块。</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> fn <span class="token operator">=</span> <span class="token function">compileFunction</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>  
<span class="token function">fn</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> nativeModuleRequire<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">,</span> process<span class="token punctuation">,</span> internalBinding<span class="token punctuation">,</span> primordials<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="5-创建一个-KoalaModule-实例"><a href="#5-创建一个-KoalaModule-实例" class="headerlink" title="5.创建一个 KoalaModule 实例"></a>5.创建一个 KoalaModule 实例</h3><p>如果不是原生 node 模块，就会当作普通文件模块加载，自己创建一个 KoalaModule 实例，去完成加载。</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> module <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">KoalaModule</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="6-添加缓存"><a href="#6-添加缓存" class="headerlink" title="6.添加缓存"></a>6.添加缓存</h3><p>我把这一小步骤单独提出的原因，想说明的是先进行缓存的添加，然后进行的模块代码的加载，这样就会出现下面的结论，Node.js 官网也有单独介绍,可以自己试一下。</p>
<ol>
<li>main 加载a，a 在真正加载前先去缓存中占一个位置</li>
<li>a 在正式加载时加载了 b</li>
<li>b 又去加载了 a，这时候缓存中已经有 a 了，所以直接返回 a.exports，这时候 exports 很有可能是不完整的内容。</li>
</ol>
<h3 id="7-module-load-真正的去加载代码"><a href="#7-module-load-真正的去加载代码" class="headerlink" title="7.module.load 真正的去加载代码"></a>7.module.load 真正的去加载代码</h3><p>不在缓存，不是原生模块，缓存已经添加完，我们通过这个 load 函数去加载文件模块，源码中位置(<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L936">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L936</a>)<br>源码中，会有一个获取文件扩展名的函数findLongestRegisteredExtension 这个方法的具体内容是有扩展名的取扩展名，没有的都按照 .js 为扩展名处理，在这个之前会判断一下 Module._extesions 支持的扩展名，不是所有都支持。<br>我们自己实现一下 load 函数。</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token class-name">KoalaModule</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">load</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">filename</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token comment">// 获取文件后缀名(我们忽略掉了findLongestRegisteredExtension函数，有兴趣小伙伴自己实现)</span>
    <span class="token keyword">const</span> extname <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">extname</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 根据不同的后缀名去进行不同的处理</span>
    KoalaModule<span class="token punctuation">.</span>_extensions<span class="token punctuation">[</span>extname<span class="token punctuation">]</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>loaded <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>获取到扩展名之后，根据不通的扩展名去执行不同的扩展名函数，源码中支持的扩展名有.js,.json,.node;</p>
<ol>
<li><p>加载.js<br> 定位到加载 .js 的源码位置 (<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1092">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1092</a>)<br> 我们自己实现一下执行.js代码。</p>
 <pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">KoalaModule<span class="token punctuation">.</span>_extensions<span class="token punctuation">[</span><span class="token string">'.js'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">module<span class="token punctuation">,</span> filename</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">const</span> content <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span>filename<span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    module<span class="token punctuation">.</span><span class="token function">_compile</span><span class="token punctuation">(</span>content<span class="token punctuation">,</span> filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p> KoalaModule._extensions 中 _compile 函数的执行。找到对应的源码位置 (<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1037">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1037</a>) ，源码中这里还使用 proxy，我们进行一下简单实现。</p>
 <pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">KoalaModule<span class="token punctuation">.</span>wrapper <span class="token operator">=</span> <span class="token punctuation">[</span>
<span class="token string">'(function (exports, require, module, __filename, __dirname) &#123; '</span><span class="token punctuation">,</span>
<span class="token string">'\n&#125;);'</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>

KoalaModule<span class="token punctuation">.</span><span class="token function-variable function">wrap</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">script</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
<span class="token keyword">return</span> KoalaModule<span class="token punctuation">.</span>wrapper<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+</span> script <span class="token operator">+</span> KoalaModule<span class="token punctuation">.</span>wrapper<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">;</span>

<span class="token class-name">KoalaModule</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">_compile</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">content<span class="token punctuation">,</span> filename</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">const</span> wrapper <span class="token operator">=</span> KoalaModule<span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 获取包装后函数体</span>

    <span class="token comment">// vm是 Node.js 的虚拟机模块，runInThisContext方法可以接受一个字符串并将它转化为一个函数</span>
    <span class="token comment">// 返回值就是转化后的函数，compiledWrapper是一个函数</span>
    <span class="token keyword">const</span> compiledWrapper <span class="token operator">=</span> vm<span class="token punctuation">.</span><span class="token function">runInThisContext</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
    filename<span class="token punctuation">,</span>
    lineOffset<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
    displayErrors<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> dirname <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 调用函数，这里一定注意传递进的内容。</span>
    <span class="token function">compiledWrapper</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>require<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">,</span>
    filename<span class="token punctuation">,</span> dirname<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p> 这里注意两个地方：</p>
<ul>
<li>使用 vm 进行模块代码的执行，模块代码外面进行了一层包裹,以便注入一些变量。<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token string">'(function (exports, require, module, __filename, __dirname) &#123; '</span><span class="token punctuation">,</span>
<span class="token string">'\n&#125;);'</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li>
<li>最终执行代码的函数传递的参数</li>
</ul>
<ol>
<li>this: compiledWrapper函数是通过 call 调用的，第一个参数就是里面的this，这里我们传入的是 this.exports，也就是 module.exports</li>
<li>exports: compiledWrapper 函数正式接收的第一个参数是 exports，我们传的也是 this.exports,所以 js 文件里面的 exports 也是对module.exports 的一个引用。</li>
<li>require: 这个方法我们传的是 this.require，其实就是KoalaModule.prototype.require 函数，也就是 KoalaModule._load。</li>
<li>module: 我们传入的是 this，也就是当前模块的实例。</li>
<li>__filename：文件所在的绝对路径。</li>
<li>__dirname: 文件所在文件夹的绝对路径。</li>
</ol>
<p> 以上两点也是我们能在 JS 模块文件里面直接使用这几个变量的原因。</p>
</li>
<li><p>加载 .json<br> 加载 .json 文件比较简单，直接使用 JSONParse就可以，定位到源码位置 (<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1117">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1117</a>) ，注意这里不要忘记异常抛出 我们简单实现下：</p>
 <pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">KoalaModule<span class="token punctuation">.</span>_extensions<span class="token punctuation">[</span><span class="token string">'.json'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">module<span class="token punctuation">,</span> filename</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">const</span> content <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span>filename<span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">&#123;</span>
        module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token function">JSONParse</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">throw</span> err<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li>
<li><p>加载 .node<br> 我们自己实现的 C++ 插件或者第三方 C++ 插件，经过编译后会变为.node 扩展名文件。.node 文件的本质是动态链接库，找到对应源码位置(<a target="_blank" rel="noopener" href="https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1135">https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1135</a>)</p>
 <pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">Module<span class="token punctuation">.</span>_extensions<span class="token punctuation">[</span><span class="token string">'.node'</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">module<span class="token punctuation">,</span> filename</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>  
    <span class="token comment">// ...  </span>
    <span class="token comment">//return process.dlopen(module, path.toNamespacedPath(filename)); </span>
<span class="token punctuation">&#125;</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>
<p> 直接调了process.dlopen，该函数在node.js里定义。</p>
 <pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> rawMethods <span class="token operator">=</span> <span class="token function">internalBinding</span><span class="token punctuation">(</span><span class="token string">'process_methods'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  
process<span class="token punctuation">.</span>dlopen <span class="token operator">=</span> rawMethods<span class="token punctuation">.</span>dlopen<span class="token punctuation">;</span>  <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p> 找到process_methods模块对应的是node_process_methods.cc。</p>
 <pre class="line-numbers language-c++" data-language="c++"><code class="language-c++">env-&gt;SetMethod(target, &quot;dlopen&quot;, binding::DLOpen);<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p> 这里也过多分析，放到 Node.js 和 C++ 那些事那篇文章一起讲解。</p>
</li>
</ol>
<h3 id="8-返回模块的-module-exports"><a href="#8-返回模块的-module-exports" class="headerlink" title="8.返回模块的 module.exports"></a>8.返回模块的 module.exports</h3><pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">return</span> module<span class="token punctuation">.</span>exports<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>在模块中的代码可以修改 module.exports 的值，不管是从缓存获取，加载原生模块还是普通文件模块，module.exports 都是最终的导出结果</p>
<h2 id="require-原理理解后的思考"><a href="#require-原理理解后的思考" class="headerlink" title="require 原理理解后的思考"></a>require 原理理解后的思考</h2><h3 id="require加载是同步还是异步"><a href="#require加载是同步还是异步" class="headerlink" title="require加载是同步还是异步"></a>require加载是同步还是异步</h3><p>这个问题我直接告诉你同步还是异步，你可能过一阵又忘记了，我之前就是这样。看过源码或者手写一遍就知道了，代码中所有文件相关操作都是使用的同步，举几个例子：</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">fs<span class="token punctuation">.</span><span class="token function">existsSync</span><span class="token punctuation">(</span>currentPath<span class="token punctuation">)</span>
fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span>filename<span class="token punctuation">,</span> <span class="token string">'utf8'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<h3 id="exports和module-exports的区别究竟是什么？"><a href="#exports和module-exports的区别究竟是什么？" class="headerlink" title="exports和module.exports的区别究竟是什么？"></a>exports和module.exports的区别究竟是什么？</h3><p>在源码中可以发现，</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token function">compiledWrapper</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>require<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">,</span>
    filename<span class="token punctuation">,</span> dirname<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>
<p>require在执行文件时候，传递的两个参数 参数一：this.exports = {}; 参数二：this 而this就是 module 所以 module.exports = {};<br>所以在一个模块没有做任何代码编写之前 exports === module.exports 都是 {} ，共同指向一个引用<br>看一张图理解这里更清楚：</p>
<p><img  srcset="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20300%20300'%3E%3C/svg%3E" data-src="https://api2.mubu.com/v3/document_image/c769a4c7-172e-4c37-aaa1-6c8e21ed684b-3807603.jpg" class="lozad post-image"src="https://api2.mubu.com/v3/document_image/c769a4c7-172e-4c37-aaa1-6c8e21ed684b-3807603.jpg"></p>
<p>但是大多数情况下我们开发时，经常会这样导出</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">exports <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
    name<span class="token operator">:</span><span class="token string">'kaola'</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
<p>或者这样写</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript">module<span class="token punctuation">.</span>export <span class="token operator">=</span> <span class="token punctuation">&#123;</span>
    value<span class="token operator">:</span>'程序员成长指北‘
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>
<p>上面这两种情况就是给 exports 或者 module.exports 重新赋值了，改变了引用地址，两个属性就不再===。<br>关键点：require一个文件，之前在手写代码时你会发现，最终导出的内容是return module.exports;.所以你对exports的重新赋值不会改变模块的导出内容，只是改变了exports这个变量而已。导出内容永远是module.exports</p>
<h3 id="require-会不会造成循环引用的问题"><a href="#require-会不会造成循环引用的问题" class="headerlink" title="require 会不会造成循环引用的问题"></a>require 会不会造成循环引用的问题</h3><p>自行去看源码实现中的第 7 步，应就懂了。</p>
<h3 id="require是怎么来的，为什么可以直接在一个文件中使用require"><a href="#require是怎么来的，为什么可以直接在一个文件中使用require" class="headerlink" title="require是怎么来的，为什么可以直接在一个文件中使用require"></a>require是怎么来的，为什么可以直接在一个文件中使用require</h3><p>require 到的文件，在 vm 模块最终执行的时，对代码进行了一层包裹，并且把对应的参数传递进去执行。<br>包裹后的代码：</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">(function (exports, require, module, __filename, __dirname) &#123; </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">$&#123;</span>script<span class="token interpolation-punctuation punctuation">&#125;</span></span><span class="token string"> \n&#125;);</span><span class="token template-punctuation string">`</span></span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
<p>执行时的代码：</p>
<pre class="line-numbers language-javascript" data-language="javascript"><code class="language-javascript"><span class="token keyword">const</span> wrapper <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">wrap</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment">// 获取包装后函数体</span>
<span class="token comment">// vm是nodejs的虚拟机模块，runInThisContext方法可以接受一个字符串并将它转化为一个函数</span>
<span class="token keyword">const</span> compiledWrapper <span class="token operator">=</span> vm<span class="token punctuation">.</span><span class="token function">runInThisContext</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">,</span> <span class="token punctuation">&#123;</span>
    filename<span class="token punctuation">,</span>
    lineOffset<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
    displayErrors<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token punctuation">&#125;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> dirname <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token function">compiledWrapper</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>exports<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>require<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">,</span>
    filename<span class="token punctuation">,</span> dirname<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>在这里小伙伴对 vm 模块有疑问的话可以看我的下一篇文章 冷门 Node.js模块 vm 学习必不可少。<br>通过代码发现 require 函数实际已经传递到了执行的 module 文件中，所以require 在 module 文件中可以直接调用了，同时也应该明白了为什么那几个变量可以直接获取了 dirname,filename 等。</p>
<h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><table>
<thead>
<tr>
<th align="left">区别</th>
<th align="left">require/exports</th>
<th align="left">import/export</th>
</tr>
</thead>
<tbody><tr>
<td align="left">出现的时间/地点不同</td>
<td align="left">2009年出生 出自CommonJS</td>
<td align="left">2015年出生 出自ECMAScript2015(ES6)</td>
</tr>
<tr>
<td align="left">不同端的使用限制</td>
<td align="left">100</td>
<td align="left">999</td>
</tr>
<tr>
<td align="left">加载方式</td>
<td align="left">同步加载，运行时动态加载，加载的是一个对象，对象需要在脚本运行完成后才会生成</td>
<td align="left">异步加载，静态编译，ES6 模块不是对象，它的对外接口只是一种静态定义，在代码静态解析阶段就会生成</td>
</tr>
<tr>
<td align="left">输出对比</td>
<td align="left">输出的是一个值的拷贝，一旦输出一个值，模块内部的变化不会影响到这个值</td>
<td align="left">输出的是值的引用，JS 引擎对脚本静态分析的时候，遇到模块加载命令import，就会生成一个只读引用。等到脚本真正执行时，再根据这个只读引用，到被加载的那个模块里面去取值。若文件引用的模块值改变，require 引入的模块值不会改变，而 import 引入的模块值会改变。</td>
</tr>
<tr>
<td align="left">使用方式</td>
<td align="left">上面手写过程中已经说了使用方式</td>
<td align="left">import的使用方式</td>
</tr>
</tbody></table>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>看源码最好带着一些目的，开篇的一些问题只知道结果并不都知道原因就是我写本文的目的，看源码的过程中也能学到一些内容，比如一些地方你会发现源码是比你写的好，嘿嘿。require 的源码中还是有很多细节点可以学习和分析的，比如这里忽略了 isMain 主文件判断，启动时候 require 的使用(这个会在另一篇文章 Node.js 的启动源码分析中介绍)，以及在 load 方法中执行模块文件时候使用到的 proxy，都可以学习下,本文是一个思路。</p>
<h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/NHYfKdxnL59j3l8zJpLV1A">彻底搞懂 Node.js 中的 Require 机制(源码分析到手写实践)</a></p>

  </div>
  <div>
    
  </div>
</article>
<div class="nav">
  
    <div class="nav-item-prev">
      <a 
        href="/2021/06/13/urlToBrowser/" 
        class="nav-link">
        <i class="iconfont icon-left nav-prev-icon"></i>
        <div>
          <div class="nav-label">上一篇</div>
          
            <div class="nav-title">输入一个url到浏览器页面展示都经历了哪些过程 </div>
          
        </div>
      </a>
    </div>
  
  
    <div class="nav-item-next">
      <a 
        href="/2021/06/13/understandTheBrowsersCachingMechanism/" 
        class="nav-link">
        <div>
          <div class="nav-label">下一篇</div>
          
            <div class="nav-title">彻底理解浏览器的缓存机制 </div>
          
        </div>
        <i class="iconfont icon-right nav-next-icon"></i>
      </a>
    </div>
  
</div>

<div 
  class="card card-content toc-card" 
  id="mobiletoc">
  <div class="toc-header">
  <i 
    class="iconfont icon-menu" 
    style="padding-right: 2px;">
  </i>目录
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-text">前言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF-CommonJS"><span class="toc-text">什么是 CommonJS</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E5%9D%97%E5%88%86%E7%B1%BB"><span class="toc-text">模块分类</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E5%9D%97%E5%8A%A0%E8%BD%BD"><span class="toc-text">模块加载</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#require-%E5%8A%A0%E8%BD%BD%E5%8E%9F%E7%90%86-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E6%89%8B%E5%86%99"><span class="toc-text">require 加载原理(源码分析与手写)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E5%9F%BA%E7%A1%80%E5%87%86%E5%A4%87%E9%98%B6%E6%AE%B5"><span class="toc-text">1.基础准备阶段</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E8%B7%AF%E5%BE%84%E5%88%86%E6%9E%90%E5%B9%B6%E5%AE%9A%E4%BD%8D%E5%88%B0%E6%96%87%E4%BB%B6"><span class="toc-text">2.路径分析并定位到文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E5%88%A4%E6%96%AD%E6%A8%A1%E5%9D%97%E6%98%AF%E5%90%A6%E5%8A%A0%E8%BD%BD%E8%BF%87-%E7%BC%93%E5%AD%98%E5%88%A4%E6%96%AD"><span class="toc-text">3.判断模块是否加载过(缓存判断)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-%E5%8E%BB%E5%8A%A0%E8%BD%BD-node-%E5%8E%9F%E7%94%9F%E6%A8%A1%E5%9D%97"><span class="toc-text">4.去加载 node 原生模块</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA-KoalaModule-%E5%AE%9E%E4%BE%8B"><span class="toc-text">5.创建一个 KoalaModule 实例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-%E6%B7%BB%E5%8A%A0%E7%BC%93%E5%AD%98"><span class="toc-text">6.添加缓存</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-module-load-%E7%9C%9F%E6%AD%A3%E7%9A%84%E5%8E%BB%E5%8A%A0%E8%BD%BD%E4%BB%A3%E7%A0%81"><span class="toc-text">7.module.load 真正的去加载代码</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-%E8%BF%94%E5%9B%9E%E6%A8%A1%E5%9D%97%E7%9A%84-module-exports"><span class="toc-text">8.返回模块的 module.exports</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#require-%E5%8E%9F%E7%90%86%E7%90%86%E8%A7%A3%E5%90%8E%E7%9A%84%E6%80%9D%E8%80%83"><span class="toc-text">require 原理理解后的思考</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#require%E5%8A%A0%E8%BD%BD%E6%98%AF%E5%90%8C%E6%AD%A5%E8%BF%98%E6%98%AF%E5%BC%82%E6%AD%A5"><span class="toc-text">require加载是同步还是异步</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#exports%E5%92%8Cmodule-exports%E7%9A%84%E5%8C%BA%E5%88%AB%E7%A9%B6%E7%AB%9F%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="toc-text">exports和module.exports的区别究竟是什么？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#require-%E4%BC%9A%E4%B8%8D%E4%BC%9A%E9%80%A0%E6%88%90%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8%E7%9A%84%E9%97%AE%E9%A2%98"><span class="toc-text">require 会不会造成循环引用的问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#require%E6%98%AF%E6%80%8E%E4%B9%88%E6%9D%A5%E7%9A%84%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E5%9C%A8%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6%E4%B8%AD%E4%BD%BF%E7%94%A8require"><span class="toc-text">require是怎么来的，为什么可以直接在一个文件中使用require</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%A1%A5%E5%85%85"><span class="toc-text">补充</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5"><span class="toc-text">参考链接</span></a></li></ol>
</div></main>
            <aside class="left-column">
              
              <div class="card card-author">
                
  <img 
    src="https://api2.mubu.com/v3/photo/654b368e-b847-4122-982c-86d90b3f5275.jpg" 
    class="author-img" 
    alt="author avatar">

<p class="author-name">霜序廿</p>
<p class="author-description">无限进步</p>
<div class="author-message">
  <a 
    class="author-posts-count" 
    href="/archives">
    <span>253</span>
    <span>文章</span>
  </a>
  <a 
    class="author-categories-count" 
    href="/categories">
    <span>6</span>
    <span>分类</span>
  </a>
  <a 
    class="author-tags-count" 
    href="/tags">
    <span>51</span>
    <span>标签</span>
  </a>
</div>

  <div class="author-card-society">
    
      <div class="author-card-society-icon">
        <a target="_blank" rel="noopener" href="https://github.com/shuangxunian">
          <i class="iconfont icon-github society-icon"></i>
        </a>
      </div>
    
      <div class="author-card-society-icon">
        <a target="_blank" rel="noopener" href="https://space.bilibili.com/391117803">
          <i class="iconfont icon-bilibili society-icon"></i>
        </a>
      </div>
    
  </div>

              </div>
               <div class="sticky-tablet">
  
  
    <article class="display-when-two-columns spacer">
      <div class="card card-content toc-card">
        <div class="toc-header">
  <i 
    class="iconfont icon-menu" 
    style="padding-right: 2px;">
  </i>目录
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-text">前言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF-CommonJS"><span class="toc-text">什么是 CommonJS</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E5%9D%97%E5%88%86%E7%B1%BB"><span class="toc-text">模块分类</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E5%9D%97%E5%8A%A0%E8%BD%BD"><span class="toc-text">模块加载</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#require-%E5%8A%A0%E8%BD%BD%E5%8E%9F%E7%90%86-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E6%89%8B%E5%86%99"><span class="toc-text">require 加载原理(源码分析与手写)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E5%9F%BA%E7%A1%80%E5%87%86%E5%A4%87%E9%98%B6%E6%AE%B5"><span class="toc-text">1.基础准备阶段</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E8%B7%AF%E5%BE%84%E5%88%86%E6%9E%90%E5%B9%B6%E5%AE%9A%E4%BD%8D%E5%88%B0%E6%96%87%E4%BB%B6"><span class="toc-text">2.路径分析并定位到文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E5%88%A4%E6%96%AD%E6%A8%A1%E5%9D%97%E6%98%AF%E5%90%A6%E5%8A%A0%E8%BD%BD%E8%BF%87-%E7%BC%93%E5%AD%98%E5%88%A4%E6%96%AD"><span class="toc-text">3.判断模块是否加载过(缓存判断)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-%E5%8E%BB%E5%8A%A0%E8%BD%BD-node-%E5%8E%9F%E7%94%9F%E6%A8%A1%E5%9D%97"><span class="toc-text">4.去加载 node 原生模块</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA-KoalaModule-%E5%AE%9E%E4%BE%8B"><span class="toc-text">5.创建一个 KoalaModule 实例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-%E6%B7%BB%E5%8A%A0%E7%BC%93%E5%AD%98"><span class="toc-text">6.添加缓存</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-module-load-%E7%9C%9F%E6%AD%A3%E7%9A%84%E5%8E%BB%E5%8A%A0%E8%BD%BD%E4%BB%A3%E7%A0%81"><span class="toc-text">7.module.load 真正的去加载代码</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-%E8%BF%94%E5%9B%9E%E6%A8%A1%E5%9D%97%E7%9A%84-module-exports"><span class="toc-text">8.返回模块的 module.exports</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#require-%E5%8E%9F%E7%90%86%E7%90%86%E8%A7%A3%E5%90%8E%E7%9A%84%E6%80%9D%E8%80%83"><span class="toc-text">require 原理理解后的思考</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#require%E5%8A%A0%E8%BD%BD%E6%98%AF%E5%90%8C%E6%AD%A5%E8%BF%98%E6%98%AF%E5%BC%82%E6%AD%A5"><span class="toc-text">require加载是同步还是异步</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#exports%E5%92%8Cmodule-exports%E7%9A%84%E5%8C%BA%E5%88%AB%E7%A9%B6%E7%AB%9F%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="toc-text">exports和module.exports的区别究竟是什么？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#require-%E4%BC%9A%E4%B8%8D%E4%BC%9A%E9%80%A0%E6%88%90%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8%E7%9A%84%E9%97%AE%E9%A2%98"><span class="toc-text">require 会不会造成循环引用的问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#require%E6%98%AF%E6%80%8E%E4%B9%88%E6%9D%A5%E7%9A%84%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E5%9C%A8%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6%E4%B8%AD%E4%BD%BF%E7%94%A8require"><span class="toc-text">require是怎么来的，为什么可以直接在一个文件中使用require</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%A1%A5%E5%85%85"><span class="toc-text">补充</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5"><span class="toc-text">参考链接</span></a></li></ol>
      </div>
    </article>
  
  
  <article class="card card-content categories-widget">
    <div class="categories-card">
  <div class="categories-header">
    <i 
      class="iconfont icon-fenlei" 
      style="padding-right: 2px;">
    </i>分类
  </div>
  <div class="categories-list">
    
      <a href="/categories/%E6%8A%80%E6%9C%AF%E6%96%87%E7%AB%A0/">
        <div class="categories-list-item">
          技术文章
          <span class="categories-list-item-badge">218</span>
        </div>
      </a>
    
      <a href="/categories/%E5%85%B6%E4%BB%96/">
        <div class="categories-list-item">
          其他
          <span class="categories-list-item-badge">16</span>
        </div>
      </a>
    
      <a href="/categories/%E6%97%85%E6%B8%B8/">
        <div class="categories-list-item">
          旅游
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
      <a href="/categories/%E7%AE%97%E6%B3%95/">
        <div class="categories-list-item">
          算法
          <span class="categories-list-item-badge">8</span>
        </div>
      </a>
    
      <a href="/categories/%E8%80%83%E8%AF%95/">
        <div class="categories-list-item">
          考试
          <span class="categories-list-item-badge">8</span>
        </div>
      </a>
    
      <a href="/categories/%E9%98%85%E8%AF%BB/">
        <div class="categories-list-item">
          阅读
          <span class="categories-list-item-badge">1</span>
        </div>
      </a>
    
  </div>
</div>
  </article>
  
  <article class="card card-content tags-widget">
    <div class="tags-card">
  <div class="tags-header">
    <i 
      class="iconfont icon-biaoqian" 
      style="padding-right: 2px;">
    </i>热门标签
  </div>
  <div class="tags-list">
    
      <a 
        href="/tags/js/" 
        title="js">
        <div class="tags-list-item">js</div>
      </a>
    
      <a 
        href="/tags/vue/" 
        title="vue">
        <div class="tags-list-item">vue</div>
      </a>
    
      <a 
        href="/tags/%E9%9D%A2%E8%AF%95/" 
        title="面试">
        <div class="tags-list-item">面试</div>
      </a>
    
      <a 
        href="/tags/css/" 
        title="css">
        <div class="tags-list-item">css</div>
      </a>
    
      <a 
        href="/tags/%E7%BD%91%E7%BB%9C/" 
        title="网络">
        <div class="tags-list-item">网络</div>
      </a>
    
      <a 
        href="/tags/%E5%85%B6%E4%BB%96/" 
        title="其他">
        <div class="tags-list-item">其他</div>
      </a>
    
      <a 
        href="/tags/%E7%AE%97%E6%B3%95/" 
        title="算法">
        <div class="tags-list-item">算法</div>
      </a>
    
      <a 
        href="/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/" 
        title="浏览器">
        <div class="tags-list-item">浏览器</div>
      </a>
    
      <a 
        href="/tags/html/" 
        title="html">
        <div class="tags-list-item">html</div>
      </a>
    
      <a 
        href="/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" 
        title="操作系统">
        <div class="tags-list-item">操作系统</div>
      </a>
    
      <a 
        href="/tags/%E8%80%83%E8%AF%95/" 
        title="考试">
        <div class="tags-list-item">考试</div>
      </a>
    
      <a 
        href="/tags/%E8%BD%AF%E5%AE%9E%E5%8A%9B/" 
        title="软实力">
        <div class="tags-list-item">软实力</div>
      </a>
    
      <a 
        href="/tags/DOM/" 
        title="DOM">
        <div class="tags-list-item">DOM</div>
      </a>
    
      <a 
        href="/tags/%E8%BD%AE%E5%AD%90/" 
        title="轮子">
        <div class="tags-list-item">轮子</div>
      </a>
    
      <a 
        href="/tags/%E7%BD%91%E7%BB%9C%E5%8E%9F%E7%90%86/" 
        title="网络原理">
        <div class="tags-list-item">网络原理</div>
      </a>
    
      <a 
        href="/tags/debug/" 
        title="debug">
        <div class="tags-list-item">debug</div>
      </a>
    
  </div>
</div>
  </article>
  
  
</div>
            </aside>
            <aside class="right-column">
              <div class="sticky-widescreen">
  
  
    <article class="card card-content toc-card">
      <div class="toc-header">
  <i 
    class="iconfont icon-menu" 
    style="padding-right: 2px;">
  </i>目录
</div>
<ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-text">前言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BB%80%E4%B9%88%E6%98%AF-CommonJS"><span class="toc-text">什么是 CommonJS</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E5%9D%97%E5%88%86%E7%B1%BB"><span class="toc-text">模块分类</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%A8%A1%E5%9D%97%E5%8A%A0%E8%BD%BD"><span class="toc-text">模块加载</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#require-%E5%8A%A0%E8%BD%BD%E5%8E%9F%E7%90%86-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B8%8E%E6%89%8B%E5%86%99"><span class="toc-text">require 加载原理(源码分析与手写)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-%E5%9F%BA%E7%A1%80%E5%87%86%E5%A4%87%E9%98%B6%E6%AE%B5"><span class="toc-text">1.基础准备阶段</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-%E8%B7%AF%E5%BE%84%E5%88%86%E6%9E%90%E5%B9%B6%E5%AE%9A%E4%BD%8D%E5%88%B0%E6%96%87%E4%BB%B6"><span class="toc-text">2.路径分析并定位到文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E5%88%A4%E6%96%AD%E6%A8%A1%E5%9D%97%E6%98%AF%E5%90%A6%E5%8A%A0%E8%BD%BD%E8%BF%87-%E7%BC%93%E5%AD%98%E5%88%A4%E6%96%AD"><span class="toc-text">3.判断模块是否加载过(缓存判断)</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-%E5%8E%BB%E5%8A%A0%E8%BD%BD-node-%E5%8E%9F%E7%94%9F%E6%A8%A1%E5%9D%97"><span class="toc-text">4.去加载 node 原生模块</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA-KoalaModule-%E5%AE%9E%E4%BE%8B"><span class="toc-text">5.创建一个 KoalaModule 实例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-%E6%B7%BB%E5%8A%A0%E7%BC%93%E5%AD%98"><span class="toc-text">6.添加缓存</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-module-load-%E7%9C%9F%E6%AD%A3%E7%9A%84%E5%8E%BB%E5%8A%A0%E8%BD%BD%E4%BB%A3%E7%A0%81"><span class="toc-text">7.module.load 真正的去加载代码</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-%E8%BF%94%E5%9B%9E%E6%A8%A1%E5%9D%97%E7%9A%84-module-exports"><span class="toc-text">8.返回模块的 module.exports</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#require-%E5%8E%9F%E7%90%86%E7%90%86%E8%A7%A3%E5%90%8E%E7%9A%84%E6%80%9D%E8%80%83"><span class="toc-text">require 原理理解后的思考</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#require%E5%8A%A0%E8%BD%BD%E6%98%AF%E5%90%8C%E6%AD%A5%E8%BF%98%E6%98%AF%E5%BC%82%E6%AD%A5"><span class="toc-text">require加载是同步还是异步</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#exports%E5%92%8Cmodule-exports%E7%9A%84%E5%8C%BA%E5%88%AB%E7%A9%B6%E7%AB%9F%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F"><span class="toc-text">exports和module.exports的区别究竟是什么？</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#require-%E4%BC%9A%E4%B8%8D%E4%BC%9A%E9%80%A0%E6%88%90%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8%E7%9A%84%E9%97%AE%E9%A2%98"><span class="toc-text">require 会不会造成循环引用的问题</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#require%E6%98%AF%E6%80%8E%E4%B9%88%E6%9D%A5%E7%9A%84%EF%BC%8C%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8F%AF%E4%BB%A5%E7%9B%B4%E6%8E%A5%E5%9C%A8%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6%E4%B8%AD%E4%BD%BF%E7%94%A8require"><span class="toc-text">require是怎么来的，为什么可以直接在一个文件中使用require</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%A1%A5%E5%85%85"><span class="toc-text">补充</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%80%BB%E7%BB%93"><span class="toc-text">总结</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5"><span class="toc-text">参考链接</span></a></li></ol>
    </article>
  
  
  <article class="card card-content">
    <div class="recent-posts-card">
  <div class="recent-posts-header">
    <i 
      class="iconfont icon-wenzhang_huaban" 
      style="padding-right: 2px;">
    </i>最近文章
  </div>
  <div class="recent-posts-list">
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-05-01</div>
        <a href="/2022/05/01/typescriptHome/"><div class="recent-posts-item-content">typescript</div></a>
      </div>
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-01-15</div>
        <a href="/2022/01/15/javaScriptTheVariousWaysAndAdvantagesAndDisadvantagesOfDeepInheritance/"><div class="recent-posts-item-content">JavaScript深入之继承的多种方式和优缺点</div></a>
      </div>
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-01-15</div>
        <a href="/2022/01/15/javaScriptGoFromPrototypeToPrototypeChain/"><div class="recent-posts-item-content">JavaScript深入之从原型到原型链</div></a>
      </div>
    
      <div class="recent-posts-item">
        <div class="recent-posts-item-title">2022-01-15</div>
        <a href="/2022/01/15/javaScriptMemoryLeakTutorial/"><div class="recent-posts-item-content">JavaScript 内存泄漏教程</div></a>
      </div>
    
  </div>
</div>
  </article>
  
  
  
  <article class="card card-content">
    <div class="recent-posts-card">
  <div class="recent-posts-header">
    关注嘉然！顿顿解馋！
  </div>
  <div class="recent-posts-list">
    
      <img 
        src="https://api2.mubu.com/v3/document_image/2697c6ae-10ee-41a3-9099-304bdb252d31-3807603.jpg" 
        class="myadd-img" 
        alt="author avatar">
    
  </div>
</div>
  </article>
</div>
            </aside>
          </div>
        </div>
      </div>
    </div>
     
    <footer class="footer">
  <div class="footer-container">
    <div>
      <div class="footer-dsc">
        <span>
          Copyright ©
          
            2020 -
          
          2022
        </span>
        &nbsp;
        <a 
          href="/" 
          class="footer-link">
          霜序廿的个人网站
        </a>
      </div>
    </div>

    
      <div class="footer-dsc">
        
          Powered by
          <a 
            href="https://hexo.io/" 
            class="footer-link" 
            target="_blank" 
            rel="nofollow noopener noreferrer">
            &nbsp;Hexo
          </a>
        
        
          <span>&nbsp;|&nbsp;</span>
        
        
          Theme -
          <a 
            href="https://github.com/theme-kaze" 
            class="footer-link" 
            target="_blank"
            rel="nofollow noopener noreferrer">
            &nbsp;Kaze
          </a>
        
      </div>
    
    
    
    
</footer> 
    
  <a 
    role="button" 
    id="scrollbutton" 
    class="basebutton" 
    aria-label="回到顶部">
    <i class="iconfont icon-arrowleft button-icon"></i>
  </a>

<a 
  role="button" 
  id="menubutton" 
  class="basebutton">
  <i class="iconfont icon-menu button-icon"></i>
</a>
<a 
  role="button" 
  id="popbutton" 
  class="basebutton" 
  aria-label="控制中心">
  <i class="iconfont icon-expand button-icon"></i>
</a>
<a 
  role="button" 
  id="darkbutton" 
  class="basebutton darkwidget" 
  aria-label="夜色模式">
  <i class="iconfont icon-weather button-icon"></i>
</a>
<a 
  role="button" 
  id="searchbutton" 
  class="basebutton searchwidget" 
  aria-label="搜索">
  <i class="iconfont icon-search button-icon"></i>
</a> 
     
     
     
      <script>
  var addImgLayout = function () {
    var img = document.querySelectorAll('.post-content img')
    var i
    for (i = 0; i < img.length; i++) {
      var wrapper = document.createElement('a')
      wrapper.setAttribute('href', img[i].getAttribute('data-src'))
      wrapper.setAttribute('aria-label', 'illustration')
      wrapper.style.cssText =
        'width: 100%; display: flex; justify-content: center;'
      if (img[i].alt) wrapper.dataset.caption = img[i].alt
      wrapper.dataset.nolink = true
      img[i].before(wrapper)
      wrapper.append(img[i])
      var divWrap = document.createElement('div')
      divWrap.classList.add('gallery')
      wrapper.before(divWrap)
      divWrap.append(wrapper)
    }
    baguetteBox.run('.gallery')
  }
</script>
<script>
  loadScript(
    "/js/lib/lightbox/baguetteBox.min.js",
    addImgLayout
  )
</script>
 
     
     
    <script src="/js/main.js"></script> 
     
    
      <script>
        var addLazyload = function () {
          var observer = lozad('.lozad', {
            load: function (el) {
              el.srcset = el.getAttribute('data-src')
            },
            loaded: function (el) {
              el.classList.add('loaded')
            },
          })
          observer.observe()
        }
      </script>
      <script>
        loadScript('/js/lib/lozad.min.js', addLazyload)
      </script>
     
    
    
  </body>
</html>
